/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.metadata.rtcustomization.servermanager.repository;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.entity.Metadata4Ref;
import com.inspur.edp.lcm.metadata.api.entity.MetadataDto;
import com.inspur.edp.lcm.metadata.api.entity.MetadataHeader;
import com.inspur.edp.lcm.metadata.api.entity.MetadataPackageHeader;
import com.inspur.edp.lcm.metadata.api.entity.MetadataProperties;
import com.inspur.edp.lcm.metadata.api.entity.MetadataReference;
import com.inspur.edp.lcm.metadata.api.entity.ServiceUnitInfo;
import com.inspur.edp.lcm.metadata.common.MetadataSerializer;
import com.inspur.edp.lcm.metadata.common.SerializerUtils;
import com.inspur.edp.lcm.metadata.common.ServiceUtils;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdContent;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdContentData;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdContentHis;
import com.inspur.edp.metadata.rtcustomization.api.entity.GspMdRefs;
import com.inspur.edp.metadata.rtcustomization.api.exception.ErrorCodes;
import com.inspur.edp.metadata.rtcustomization.api.exception.LcmMetadataException;
import com.inspur.edp.metadata.rtcustomization.api.exception.LcmParseException;
import com.inspur.edp.metadata.rtcustomization.servermanager.CustomizationServiceContext;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.CustomizationDataProducer;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.CustomizationMetadataRefsRepository;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.CustomizeRepo;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.MetadataContentHisRepository;
import com.inspur.edp.metadata.rtcustomization.servermanager.dac.MetadataContentRepository;
import io.iec.edp.caf.commons.transaction.JpaTransaction;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 此类为运行时定制服务与持久化存储之间的层次。用于组装、分解相关实体内容
 *
 * @author zhaoleitr
 */
@Slf4j
public class CustomizationRepoService {

	private final CustomizeRepo customizeRepo;
	private final MetadataContentRepository mdContentRepo;
	private final MetadataContentHisRepository mdContentHisRepo;
	private final CustomizationMetadataRefsRepository metadataRefsRepository;
	private final MetadataRefsRepoService metadataRefsRepoService;

	public CustomizationRepoService(MetadataContentRepository mdContentRepo, MetadataContentHisRepository mdContentHisRepo,
                                    CustomizationDataProducer cdp, CustomizationMetadataRefsRepository metadataRefsRepository, MetadataRefsRepoService metadataRefsRepoService) {
		customizeRepo = new CustomizeRepo(cdp);
		this.mdContentRepo = mdContentRepo;
		this.mdContentHisRepo = mdContentHisRepo;
		this.metadataRefsRepository=metadataRefsRepository;
		this.metadataRefsRepoService = metadataRefsRepoService;
	}

	/**
	 * 从gspmdcontent表中获取元数据，版本为当前系统中最新版本
	 */
	public GspMetadata getCurrentMetadata(String metadataId, String certId) {
		GspMdContent mdContent;
		if (StringUtils.isEmpty(certId)) {
			mdContent = mdContentRepo.findByMetadataId(metadataId);
		} else {
			mdContent = mdContentRepo.findByMetadataIdAndCertId(metadataId, certId);
		}

		if (mdContent == null) {
			return null;
		}

		return buildGspMetadataFromGspMdContent(mdContent);
	}

	public GspMdContent findByMetadataId(String metadataId) {
		return mdContentRepo.findByMetadataId(metadataId);
	}

	public List<MetadataDto> findAllBySourcetypeAndBizObjID(Integer sourceType, String bizObjID) {
		List<GspMdContentData> gspMdContentDataList = customizeRepo.findAllBySourcetypeAndBizObjID(sourceType,bizObjID);
		List<MetadataDto> metadataDtoList = new ArrayList<>();
		if(!CollectionUtils.isEmpty(gspMdContentDataList)){
			// 查询gspmdrefs信息
			List<String> mdIdList = gspMdContentDataList.stream().map(GspMdContentData::getMetadataId).collect(Collectors.toList());
			// oracle数据库1000限制
			List<List<String>> mdIdGroupList = new ArrayList<>();
			if(mdIdList.size() > 999){
				mdIdGroupList.addAll(splistList(mdIdList, 999));
			}else {
				mdIdGroupList.add(mdIdList);
			}
			List<GspMdRefs> gspMdRefsList = new ArrayList<>();
			for(List<String> mdIds : mdIdGroupList){
				gspMdRefsList.addAll(metadataRefsRepository.findAllByMdIdIn(mdIds));
			}
			Map<String,List<MetadataReference>> mdId2refsMap = buildMetadataReference(gspMdRefsList);
			gspMdContentDataList.forEach(item -> {
				MetadataDto metadataDto = asMetadataDto(item);
				ObjectMapper objectMapper = ServiceUtils.getMapper();
				if (!CollectionUtils.isEmpty(mdId2refsMap.get(item.getMetadataId()))) {
					try {
						metadataDto.setRefs(objectMapper.writeValueAsString(mdId2refsMap.get(item.getMetadataId())));
					} catch (JsonProcessingException e) {
						throw new LcmParseException(e, ErrorCodes.ECP_PARSE_0009, item.getMetadataId());
					}
				}
				metadataDtoList.add(metadataDto);
			});
		}
		return metadataDtoList;
	}

	private <T> List<List<T>> splistList(List<T> list,int subNum) {
		List<List<T>> tNewList = new ArrayList<List<T>>();
		int priIndex = 0;
		int lastPriIndex = 0;
		int insertTimes = list.size()/subNum;
		List<T> subList = new ArrayList<>();
		for (int i = 0;i <= insertTimes;i++) {
			priIndex = subNum*i;
			lastPriIndex = priIndex + subNum;
			if (i == insertTimes) {
				subList = list.subList(priIndex,list.size());
			} else {
				subList = list.subList(priIndex,lastPriIndex);
			}
			if (subList.size() > 0) {
				tNewList.add(subList);
			}
		}
		return tNewList;
	}

	private Map<String,List<MetadataReference>> buildMetadataReference(List<GspMdRefs> refs) {
		Map<String,List<MetadataReference>> mdId2refsMap = new HashMap<>();
		refs.forEach(item -> {
			MetadataReference ref = new MetadataReference();
			ref.setMetadata(new MetadataHeader());
			ref.setDependentMetadata(new MetadataHeader());
			ref.setReferenceDetail(new ArrayList<>());
			ref.getMetadata().setId(item.getMdId());
			ref.getMetadata().setCode(item.getMdCode());
			ref.getMetadata().setNameSpace(item.getMdNameSpace());
			ref.getMetadata().setType(item.getMdType());
			ref.getDependentMetadata().setId(item.getRefMdId());
			ref.getDependentMetadata().setNameSpace(item.getRefMdNameSpace());
			ref.getDependentMetadata().setCode(item.getRefMdCode());
			ref.getDependentMetadata().setType(item.getRefMdType());
			if(Objects.isNull(mdId2refsMap.get(ref.getMetadata().getId()))){
				mdId2refsMap.put(ref.getMetadata().getId(), new ArrayList<MetadataReference>());
			}
			mdId2refsMap.get(ref.getMetadata().getId()).add(ref);
		});

		return mdId2refsMap;
	}

	public void saveExtContent(GspMetadata extendMetadataEntity) {
		GspMdContent currentVersionMdContent;
		if (StringUtils.isEmpty(extendMetadataEntity.getHeader().getCertId())) {
			currentVersionMdContent = mdContentRepo.findByMetadataId(extendMetadataEntity.getHeader().getId());
		} else {
			currentVersionMdContent = mdContentRepo.findByMetadataIdAndCertId(extendMetadataEntity.getHeader().getId(), extendMetadataEntity.getHeader().getCertId());
		}

		GspMdContentHis contentHis = null;
		if (currentVersionMdContent != null) {
			contentHis = buildGspMdContentHisFromGspMdContent(currentVersionMdContent);
		}
		String id;
		if (currentVersionMdContent != null) {
			id = currentVersionMdContent.getId();
		} else {
			id = UUID.randomUUID().toString();
		}
		GspMdContent mdContent = buildGspMdContentFromGspMetdata(extendMetadataEntity, id,true);
		// TODO 扩展中的元数据是否需要保存依赖关系，和运行时元数据的扩展关系是否冲突？
		List<GspMdRefs> metadataRefs = metadataRefsRepoService.buildGspMdRefsByMetadata(extendMetadataEntity);

		JpaTransaction tran = JpaTransaction.getTransaction();
		try{
			tran.begin();
			mdContentRepo.save(mdContent);
			metadataRefsRepoService.deleteByMdId(extendMetadataEntity.getHeader().getId());
			metadataRefsRepoService.saveAll(metadataRefs);
			if (contentHis != null) {
				mdContentHisRepo.save(contentHis);
			}
			tran.commit();
		} catch (Throwable e) {
            try {
                tran.rollback();
            } catch (Exception ex) {
                log.error("saveExtContent rollback error", ex);
            }
			throw new LcmMetadataException(e, ErrorCodes.ECP_METADATA_0013, mdContent.getMetadataId(), mdContent.getNameSpace(), mdContent.getCode());
		}
	}

	private GspMdContentHis buildGspMdContentHisFromGspMdContent(GspMdContent currentVersionMdContent) {
		GspMdContentHis mdContentHis = new GspMdContentHis();
		mdContentHis.setId(UUID.randomUUID().toString());
		mdContentHis.setMetadataId(currentVersionMdContent.getMetadataId());
		mdContentHis.setBizObjId(currentVersionMdContent.getBizObjId());
		mdContentHis.setCertId(currentVersionMdContent.getCertId());
		mdContentHis.setCode(currentVersionMdContent.getCode());
		mdContentHis.setContent(currentVersionMdContent.getContent());
		mdContentHis.setCreateBy(currentVersionMdContent.getCreateBy());
		mdContentHis.setVersion(currentVersionMdContent.getVersion());
		mdContentHis.setPreviousVersion(currentVersionMdContent.getPreviousVersion());
		mdContentHis.setExtended(currentVersionMdContent.isExtended());
		mdContentHis.setName(currentVersionMdContent.getName());
		mdContentHis.setType(currentVersionMdContent.getType());
		mdContentHis.setNameSpace(currentVersionMdContent.getNameSpace());
		mdContentHis.setExtendProperty(currentVersionMdContent.getExtendProperty());
		mdContentHis.setProperties(currentVersionMdContent.getProperties());
		return mdContentHis;
	}

	public GspMdContent buildGspMdContentFromGspMetdata(GspMetadata metadata, String rowId,boolean isExtended) {
		GspMdContent mdContent = new GspMdContent();
		mdContent.setId(rowId);
		mdContent.setMetadataId(metadata.getHeader().getId());
		mdContent.setCertId(metadata.getHeader().getCertId());
		mdContent.setVersion(metadata.getVersion());
		mdContent.setPreviousVersion(metadata.getPreviousVersion());
		mdContent.setCode(metadata.getHeader().getCode());
		mdContent.setName(metadata.getHeader().getName());
		mdContent.setType(metadata.getHeader().getType());
		mdContent.setBizObjId(metadata.getHeader().getBizobjectID());
		mdContent.setExtended(isExtended);
		mdContent.setNameSpace(metadata.getHeader().getNameSpace());
		mdContent.setExtendProperty(metadata.getExtendProperty());
		mdContent.setContent(new MetadataSerializer().serialize(metadata));
		mdContent.setCreateBy(CustomizationServiceContext.getUserName());
		mdContent.setCreatedOn(LocalDateTime.now());
		mdContent.setLastChangedBy(CustomizationServiceContext.getUserName());
		mdContent.setLastChangedOn(LocalDateTime.now());
		mdContent.setProperties(metadata.getProperties());
		return mdContent;
	}

	public GspMetadata buildGspMetadataFromGspMdContent(GspMdContent mdContent) {
		GspMetadata metadata = new MetadataSerializer().deserialize(mdContent.getContent(), GspMetadata.class);
		metadata.setVersion(mdContent.getVersion());
		metadata.setExtended(mdContent.isExtended());
		if (metadata.getProperties() == null) {
			metadata.setProperties(new MetadataProperties("", mdContent.getLastChangedOn().toString()));
		} else {
			metadata.getProperties().setCacheVersion(mdContent.getLastChangedOn().toString());
		}
		return metadata;
	}

	private MetadataDto asMetadataDto(GspMdContentData gspMdContentData) {
		MetadataDto metadataDto = new MetadataDto();
		metadataDto.setId(gspMdContentData.getMetadataId());
		metadataDto.setBizobjectID(gspMdContentData.getBizObjId());
		metadataDto.setExtented(gspMdContentData.isExtended());
		metadataDto.setVersion(gspMdContentData.getVersion());
		metadataDto.setCode(gspMdContentData.getCode());
		metadataDto.setName(gspMdContentData.getName());
		metadataDto.setType(gspMdContentData.getType());
		metadataDto.setNameSpace(gspMdContentData.getNameSpace());
		metadataDto.setProperties(gspMdContentData.getProperties());
		return metadataDto;
	}

	public void deleteCustomizedData(String id, String certId) {
		if (StringUtils.isEmpty(certId)) {
			mdContentRepo.deleteByMetadataId(id);
		} else {
			mdContentRepo.deleteByMetadataIdAndCertId(id, certId);
		}
	}

	public List<GspMdContent> getCustomizedListByIdAndCertId(String id, String certid, String version) {
		return customizeRepo.getCustomizedListByIdAndCertId(id, certid, version);
	}

	public List<GspMdContent> getMetadataInfos(String nameSpace, String code, String type) {
		List<GspMdContent> customizedMetadataList = customizeRepo.getAllCustomizedList();
		if (customizedMetadataList == null || customizedMetadataList.size() == 0) {
			return null;
		}
		List<GspMdContent> metadataList = new ArrayList<>();

		customizedMetadataList.forEach(item -> {
			boolean nameSpaceEqual = (!StringUtils.isEmpty(nameSpace) && nameSpace.equals(item.getNameSpace()))
					|| (StringUtils.isEmpty(nameSpace) && StringUtils.isEmpty(item.getNameSpace()));
			if (nameSpaceEqual && code.equals(item.getCode()) && type.equals(item.getType())) {
				metadataList.add(item);
			}
		});

		return metadataList;
	}

	public List<Metadata4Ref> getAllCustomizedMetadata(String metadataTypes) {
		String[] types = metadataTypes.split(",");
		List<String> typeList = new ArrayList<>(Arrays.asList(types));
		return getAllCustomizedMetadata(typeList, false);
	}

	public List<Metadata4Ref> getAllCustomizedMetadata(List<String> metadataTypes, boolean isNoPackageInfo) {
		List<GspMdContent> customizedMetadataList = customizeRepo.getAllCustomizedList();
		if (customizedMetadataList == null || customizedMetadataList.size() == 0) {
			return null;
		}
		List<Metadata4Ref> metadataList = new ArrayList<>();
		customizedMetadataList.forEach(item -> {
			if(!CollectionUtils.isEmpty(metadataTypes)){
				Optional<String> md = metadataTypes.stream().filter(type -> type.equalsIgnoreCase(item.getType())).findFirst();
				if (md.isPresent()) {
					if(isNoPackageInfo){
						var metadata = buildMetadata4RefByGspMdConten(item, null);
						metadataList.add(metadata);
					}else{
						var metadata = buildMetadata4RefByGspMdConten(item);
						metadataList.add(metadata);
					}
				}
			}else{
				if(isNoPackageInfo){
					var metadata = buildMetadata4RefByGspMdConten(item, null);
					metadataList.add(metadata);
				}else{
					var metadata = buildMetadata4RefByGspMdConten(item);
					metadataList.add(metadata);
				}
			}

		});

		return metadataList;
	}

	public List<Metadata4Ref> getAllCustomizedMetadata() {

		List<GspMdContent> customizedMetadataList = customizeRepo.getAllCustomizedList();
		if (customizedMetadataList == null || customizedMetadataList.size() == 0) {
			return null;
		}
		List<Metadata4Ref> metadataList = new ArrayList<>();
		customizedMetadataList.forEach(item -> {
			var metadata = buildMetadata4RefByGspMdConten(item);
			metadataList.add(metadata);
		});

		return metadataList;
	}

	private Metadata4Ref buildMetadata4RefByGspMdConten(GspMdContent metadata) {
		return buildMetadata4RefByGspMdConten(metadata,new MetadataPackageHeader());
	}

	private Metadata4Ref buildMetadata4RefByGspMdConten(GspMdContent gspMdContent, MetadataPackageHeader metadataPackageHeader) {
		Metadata4Ref metadata4Ref = new Metadata4Ref();
		metadata4Ref.setMetadata(new GspMetadata());
		metadata4Ref.getMetadata().setHeader(new MetadataHeader());
		if(Objects.nonNull(metadataPackageHeader)){
			metadata4Ref.setPackageHeader(metadataPackageHeader);
		}
		metadata4Ref.setServiceUnitInfo(new ServiceUnitInfo());
		metadata4Ref.getMetadata().getHeader().setId(gspMdContent.getMetadataId());
		metadata4Ref.getMetadata().getHeader().setName(gspMdContent.getName());
		metadata4Ref.getMetadata().getHeader().setCode(gspMdContent.getCode());
		metadata4Ref.getMetadata().getHeader().setType(gspMdContent.getType());
		metadata4Ref.getMetadata().getHeader().setBizobjectID(gspMdContent.getBizObjId());
		metadata4Ref.getMetadata().getHeader().setNameSpace(gspMdContent.getNameSpace());
		metadata4Ref.getMetadata().setExtendProperty(gspMdContent.getExtendProperty());
		metadata4Ref.getMetadata().setExtended(gspMdContent.isExtended());
		metadata4Ref.getMetadata().setProperties(gspMdContent.getProperties());
		return metadata4Ref;
	}

	public GspMetadata buildMetadata4RefFromGspMdContent(GspMdContent mdContent) {
		GspMetadata metadata = new GspMetadata();
		metadata.setHeader(new MetadataHeader());
		metadata.getHeader().setId(mdContent.getMetadataId());
		metadata.getHeader().setName(mdContent.getName());
		metadata.getHeader().setCode(mdContent.getCode());
		metadata.getHeader().setType(mdContent.getType());
		metadata.getHeader().setBizobjectID(mdContent.getBizObjId());
		metadata.getHeader().setCertId(mdContent.getCertId());
		metadata.getHeader().setNameSpace(mdContent.getNameSpace());
		metadata.setExtendProperty(mdContent.getExtendProperty());
		metadata.setVersion(mdContent.getVersion());
		metadata.setExtended(mdContent.isExtended());
		metadata.setProperties(mdContent.getProperties());

		return metadata;
	}

	public List<GspMdContent> findAllLastChangedOn() {
		return mdContentRepo.findAllLastChangedOn();
	}

	//endregion
}
